#pragma once

#include <functional>
#include <memory>

#include "TermSystem.hpp"
#include "TermCache.hpp"

namespace terms {
	/**
	 * \brief Term factory for singleton terms.
	 *
	 * \tparam BaseTerm The base term of the \ref TermSystem this TermFactory can
	 * create.
	 **/
	template <class BaseTerm>
	class TermFactory {
	private:
		std::shared_ptr<TermCache<BaseTerm>> _term_cache;

	public:
		/**
		 * \brief Construct a new, standalone term factory.
		 **/
		TermFactory()
			: _term_cache(new TermCache<BaseTerm>())
		{
		}

	public:
		/**
		 * \brief Copy the supplied term factory.
		 *
		 * The existing and the new term factory will share 
		 * a term cache.
		 *
		 * This is the same as \code{.cpp}TermCache(factory, false)\endcode.
		 **/
		TermFactory(const TermFactory &factory)
			: _term_cache(factory._term_cache)
		{
		}

	public:
		/**
		 * \brief Construct a new term factory from an existing term_factory
		 *
		 * if \p wrap_cache is true, a new TermCache is constructed
		 * for the new factory, using the cache of the other factory
		 * as a base cache. If \p wrap_cahe is false, both factories
		 * will share a reference to the same cache.
		 **/
		TermFactory(const TermFactory &_factory, bool wrap_cache)
		{
			if (wrap_cache) {
				_term_cache = std::shared_ptr<TermCache<BaseTerm>>(new TermCache<BaseTerm>(_factory._term_cache));
			} else {
				_term_cache = std::shared_ptr<TermCache<BaseTerm>>(_factory._term_cache);
			}
		}

	public:
		/*!
		 * \brief Create a new term of the specified type
		 *
		 * Accepts an arbitrary initializer list and forward it
		 * to the target term's contructor.
		 **/
		template<class Term, class Arg>
		const Term *
		create(std::initializer_list<Arg> args)
		{
			std::unique_ptr<Term> term_ptr(new Term(args));
			return _term_cache->get(term_ptr);
		}

	public:
		/**
		 * \brief Create a new term of the specified type.
		 *
		 * Uses perfect forwarding to forward the argument list to the
		 * target term's constructor.
		 *
		 * \tparam Term The type of term to create
		 * \tparam Args The argument pack to be forwarded to the Term's
		 * constructor. 
		 **/
		template <typename Term, typename... Args>
		const Term *
		create(Args ... args)
		{
			std::unique_ptr<Term> term_ptr(new Term{std::forward<Args>(args)...});
			return _term_cache->get(term_ptr);
		}
	};
}
